"""GA算法相关"""
import random
import numpy as np

from dytz.params import *

__all__ = ['get_random', 'selection', 'OneBack']


def get_random(p: Param) -> float or int:
    """
    获取随机数
    :param p: Param类， y依据该类的信息生成随机数
    :return:
    """
    dtype = p.dtype
    if p.default is not None:
        d = dtype(p.default)
    elif p.choices is not None:
        d = dtype(random.choice(p.choices))
    else:
        _min = p.dtype(p.min)
        _max = p.dtype(p.max)
        if dtype == int:
            d = random.randint(_min, _max)
        elif dtype == float:
            n = max(len(str(_max).split('.')[1]), len(str(_min).split('.')[1]))
            n = int('1' + '0' * n)
            d = np.arange(_min, _max + 1 / n, 1 / n)
            d = random.choice(d)
        else:
            d = None
    return d


def selection(pop, n):
    return np.random.choice(pop, n)


class OneBack:
    """
    回测个体基类， 代表每个带参数的策略
    """

    def __init__(self, par: dict):
        """
        构造器
        :param par: 参数字典
        """
        self.p = par
        self.params = {}
        for p in self.p:
            _p = self.p[p]
            self.params[p] = get_random(_p)
        self.encode()
        self.res_val = None

    def __repr__(self) -> str:
        return f'params:{self.p}; val:{self.res_val}'

    def encode(self) -> None:
        """编码， 依据参数，生成二进制编码"""
        self.params_code = {}
        for p in self.p:
            _p = self.p[p]
            self.params_code[p] = self.__encode(_p, self.params[p])

    def decode(self) -> None:
        """解码，依据二进制编码，还原参数"""
        self.params = {}
        for p in self.p:
            _p = self.p[p]
            self.params[p] = self.__decode(_p, self.params_code[p])
        self.encode()

    def __encode(self, par: Param, val: str or float) -> str:
        """
        依据Param类对参数进行编码
        :param par: Param类，主要参考参数的个数
        :param val: 参数值
        :return: 二进制字符串
        """
        if par.dtype == int:
            if par.default is not None:
                return '1'
            elif par.choices is not None:
                v = par.choices
                _n = len(v) - 1
                _v = v.index(val)
                e = bin(_v)[2:].zfill(_n)
            else:
                _min = par.dtype(par.min)
                _max = par.dtype(par.max)
                v = list(range(_min, _max + 1))
                _n = len(v) - 1
                _v = v.index(val)
                e = bin(_v)[2:].zfill(_n)
        else:
            if par.default is not None:
                return '1'
            elif par.choices is not None:
                v = par.choices
                _n = len(v) - 1
                _v = v.index(val)
                e = bin(_v)[2:].zfill(_n)
            else:
                _min = par.dtype(par.min)
                _max = par.dtype(par.max)
                n = max(len(str(_max).split('.')[1]), len(str(_min).split('.')[1]))
                n = int('1' + '0' * n)
                v = list(np.arange(_min, _max + 1 / n, 1 / n))
                _n = len(v) - 1
                _v = v.index(val)
                e = bin(_v)[2:].zfill(_n)
        return e

    def __decode(self, par: Param, code: str) -> float or str:
        """
        依据Param类对参数进行解码
        :param par: Param类，主要参考参数的个数
        :param code: 二进制编码字符串
        :return: 参数
        """
        if par.dtype == int:
            if par.default is not None:
                return par.default
            elif par.choices is not None:
                v = par.choices
                _n = len(v) - 1
                c = int('0b' + code, 2)
                if c <= _n:
                    va = v[c]
                else:
                    va = random.choice(v)
            else:
                _min = par.dtype(par.min)
                _max = par.dtype(par.max)
                v = list(range(_min, _max + 1))
                _n = len(v) - 1
                c = int('0b' + code, 2)
                if c <= _n:
                    va = v[c]
                else:
                    va = random.choice(v)
        else:
            if par.default is not None:
                return par.default
            elif par.choices is not None:
                v = par.choices
                _n = len(v) - 1
                c = int('0b' + code, 2)
                if c <= _n:
                    va = v[c]
                else:
                    va = random.choice(v)
            else:
                _min = par.dtype(par.min)
                _max = par.dtype(par.max)
                n = max(len(str(_max).split('.')[1]), len(str(_min).split('.')[1]))
                n = int('1' + '0' * n)
                v = list(np.arange(_min, _max + 1 / n, 1 / n))
                _n = len(v) - 1
                c = int('0b' + code, 2)
                if c <= _n:
                    va = v[c]
                else:
                    va = random.choice(v)
        return va

    def aber(self, r: float = 0.10) -> bool:
        """
        变异
        :param r: 变异率。位于0到1之间
        :return: 是否变异
        """
        kk = 0
        for p in self.p:
            if random.uniform(0, 1) <= r:
                kk += 1
                _p = self.params_code[p]
                n = len(_p)
                k1 = random.randint(0, n)
                k2 = min(3, random.randint(0, n - k1))
                k1, k2 = min(k1, k2), max(k1, k2)
                nc = ''
                for k_ in range(len(_p)):
                    if k_ >= k1 and k_ < k1 + k2:
                        if _p[k_] == '0':
                            nc += '1'
                        else:
                            nc += '0'
                    else:
                        nc += _p[k_]
                try:
                    self.__decode(self.p[p], nc)
                    self.params_code[p] = nc
                except ValueError:
                    pass
        self.decode()
        if kk == 0:
            return False
        else:
            return True

    def __add__(self, other) -> tuple:
        """
        重置＋号代表杂交过程
        :param other: 另一个OneBack
        :return: 两个OneBack的元组
        """
        o1 = OneBack(self.p)
        o2 = OneBack(self.p)
        for p in self.p:
            _p1 = self.params_code[p]
            _p2 = other.params_code[p]
            n = len(_p1)
            k1 = random.randint(0, n) - 1
            k2 = min(3, random.randint(0, n - k1))
            _k1 = _p1[k2: k1 + k2]
            _k2 = _p2[k2: k1 + k2]
            _p1 = _p1[:k2] + _k2 + _p1[k1 + k2:]
            _p2 = _p2[:k2] + _k1 + _p2[k1 + k2:]
            o1.params_code['p'] = _p1
            o2.params_code['p'] = _p2
        o1.decode()
        o2.decode()
        return o1, o2
